﻿using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using Microsoft.SharePoint.Workflow;
using System.Workflow.ComponentModel.Compiler;
using System.ComponentModel.Design;

namespace TPG.SharePoint.Workflow.Controls
{
    [ToolboxItem(typeof(ActivityToolboxItem)),
    Designer(typeof(ExtractDataDesigner))]
    public class ExtractData
        : Activity, IDynamicPropertyTypeProvider
    {
        public static readonly DependencyProperty WorkflowPropertiesProperty =
            DependencyProperty.Register("WorkflowProperties",
            typeof(SPWorkflowActivationProperties),
            typeof(ExtractData));
        public static readonly DependencyProperty ParameterBindingsProperty =
            DependencyProperty.Register("ParameterBindings",
            typeof(WorkflowParameterBindingCollection),
            typeof(ExtractData),
            new PropertyMetadata(DependencyPropertyOptions.Metadata | DependencyPropertyOptions.ReadOnly, new Attribute[] { new BrowsableAttribute(false), new DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Content) }));

        [Category("Activity")]
        [Browsable(true)]
        [Editor(typeof(TypeBrowserEditor), typeof(UITypeEditor))]
        [RefreshProperties(RefreshProperties.All)]
        [ValidationOption(ValidationOption.Required)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public Type AssociationDataType
        {
            get;
            set;
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Browsable(false)]
        public WorkflowParameterBindingCollection ParameterBindings
        {
            get
            {
                return base.GetValue(ParameterBindingsProperty) as WorkflowParameterBindingCollection;
            }
        }

        public SPWorkflowActivationProperties WorkflowProperties
        {
            get { return (SPWorkflowActivationProperties)GetValue(WorkflowPropertiesProperty); }
            set 
            {
                base.SetValue(ExtractData.WorkflowPropertiesProperty, value);
                if (value != null)
                {
                    string data = value.AssociationData;
                    if (ParameterBindings.Contains("AssociationData"))
                    {
                        WorkflowParameterBinding binding = ParameterBindings["AssociationData"];
                        binding.SetValue(WorkflowParameterBinding.ValueProperty,
                            Deserialize(data));
                    }
                }
            }
        }

        private object Deserialize(string data)
        {
            SerializationMode mode = SerializationMode.Xml;
            string serializationModeString = WorkflowConfiguration.LookupMetadata(
                WorkflowProperties.Workflow.ParentAssociation.BaseTemplate,
                PageType.Association, String.Empty, String.Empty,
                WorkflowConfiguration.DataTypeSerializationMode, false);
            if (String.IsNullOrEmpty(serializationModeString) == false)
            {
                mode = EnumUtils.SafeParseEnum<SerializationMode>(serializationModeString);
            }
            if (mode == SerializationMode.Xml)
            {
                return WorkflowDataSerializer.DeserializeFromXmlString(data, AssociationDataType);
            }
            else
            {
                return WorkflowDataSerializer.DeserializeFromBase64String(data);
            }
        }

        public ExtractData()
        {
            SetReadOnlyPropertyValue(ParameterBindingsProperty,
                new WorkflowParameterBindingCollection(this));
        }


        public AccessTypes GetAccessType(IServiceProvider serviceProvider, string propertyName)
        {
            return AccessTypes.Read;
        }

        public Type GetPropertyType(IServiceProvider serviceProvider, string propertyName)
        {
            if (propertyName == "AssociationData")
            {
                return AssociationDataType;
            }
            return null;
        }
    }

    public class ExtractDataDesigner
        : ActivityDesigner
    {
        protected override void PreFilterProperties(System.Collections.IDictionary properties)
        {
            ExtractData activity = (ExtractData)Activity;
            base.PreFilterProperties(properties);
            if (activity.AssociationDataType != null)
            {
                properties.Add("AssociationData",
                    new ExtractDataProperty(
                        new PropertyConfig()
                        {
                            Name = "AssociationData",
                            DataType = activity.AssociationDataType
                        }));
            }
        }
    }

    public class PropertyConfig
    {
        public string Name { get; set; }
        public Type DataType { get; set; }
    }

    public class ExtractDataProperty
        : PropertyDescriptor
    {
        PropertyConfig _config;

        public override Type ComponentType
        {
            get { return typeof(ExtractData); }
        }

        public override bool IsReadOnly
        {
            get { return false; }
        }

        public override Type PropertyType
        {
            get { return _config.DataType; }
        }

        public ExtractDataProperty(PropertyConfig config)
            : base(config.Name, new Attribute[] { DesignOnlyAttribute.Yes })
        {
            _config = config;
        }

        public override bool CanResetValue(object component)
        {
            return false;
        }

        public override object GetValue(object component)
        {
            WorkflowParameterBindingCollection bindings =
                ((ExtractData)component).ParameterBindings;
            if (bindings.Contains(Name))
            {
                WorkflowParameterBinding binding = bindings[Name];
                if (binding.IsBindingSet(WorkflowParameterBinding.ValueProperty))
                {
                    return binding.GetBinding(WorkflowParameterBinding.ValueProperty);
                }
                else
                {
                    return binding.GetValue(WorkflowParameterBinding.ValueProperty);
                }
            }
            return null;
        }

        public override void ResetValue(object component)
        {
        }

        public override void SetValue(object component, object value)
        {
            IServiceProvider serviceProvider = GetSite(component);
            ComponentChangeDispatcher componentChange = (serviceProvider != null) ?
                new ComponentChangeDispatcher(serviceProvider, component, this) : null;

            try
            {
                WorkflowParameterBindingCollection parameters = (component as ExtractData).ParameterBindings;
                if (parameters != null)
                {
                    WorkflowParameterBinding binding = null;
                    if (parameters.Contains(Name))
                        binding = parameters[Name];
                    else
                    {
                        binding = new WorkflowParameterBinding(Name);
                        parameters.Add(binding);
                    }

                    if (value is ActivityBind)
                        binding.SetBinding(WorkflowParameterBinding.ValueProperty, value as ActivityBind);
                    else
                        binding.SetValue(WorkflowParameterBinding.ValueProperty, value);

                    OnValueChanged(component, EventArgs.Empty);
                }
            }
            finally
            {
                if (componentChange != null)
                    componentChange.Dispose();
            }
        }

        public override bool ShouldSerializeValue(object component)
        {
            return true;
        }

        public override TypeConverter Converter
        {
            get
            {
                return new ActivityBindTypeConverter();
            }
        }
    }

    class ComponentChangeDispatcher : IDisposable
    {
        private IServiceProvider serviceProvider;
        private object component;
        private PropertyDescriptor property;
        private object oldValue;
        private object newValue;

        public ComponentChangeDispatcher(IServiceProvider serviceProvider, object component, PropertyDescriptor propertyDescriptor)
        {
            this.serviceProvider = serviceProvider;
            this.component = component;
            this.property = propertyDescriptor;

            IComponentChangeService changeService = serviceProvider.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
            if (changeService != null)
            {
                try
                {
                    newValue = oldValue = propertyDescriptor.GetValue(component);
                    propertyDescriptor.AddValueChanged(component, new EventHandler(OnValueChanged));
                    changeService.OnComponentChanging(component, propertyDescriptor);
                }
                catch (CheckoutException coEx)
                {
                    if (coEx == CheckoutException.Canceled)
                        return;
                    throw coEx;
                }
            }
        }

        public void Dispose()
        {
            IComponentChangeService changeService = this.serviceProvider.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
            if (changeService != null)
                changeService.OnComponentChanged(this.component, this.property, this.oldValue, this.newValue);
        }

        private void OnValueChanged(object sender, EventArgs e)
        {
            this.newValue = this.property.GetValue(this.component);
            this.property.RemoveValueChanged(this.component, new EventHandler(OnValueChanged));
        }
    }
}
